package nc.bs.gl.validate;

import nc.bs.common.util.DaoUtil;
import nc.bs.common.util.GlBalanceUtil;
import nc.bs.common.util.GlDocFreeItem;
import nc.bs.common.util.ObjectUtils;
import nc.bs.common.util.TimeWatch;
import nc.bs.common.util.pojo.SettleRecord;
import nc.bs.common.util.pojo.TempAccountDetail;
import nc.bs.framework.common.InvocationInfoProxy;
import nc.bs.framework.common.RuntimeEnv;
import nc.bs.gl.util.GlDetailQueryBuilder;
import nc.bs.gl.util.GlDetailQueryUtil;
import nc.bs.lang.util.NCDefLangUtil;
import nc.bs.logging.Logger;
import nc.itf.scmpub.reference.uap.bd.accesor.CustomerAccessor;
import nc.itf.scmpub.reference.uap.bd.accesor.PsnDocAccessor;
import nc.itf.scmpub.reference.uap.bd.accesor.SupplierAccessor;
import nc.jdbc.framework.processor.MapProcessor;
import nc.pubitf.para.SysInitQuery;
import nc.vo.glcom.reckoning.MonthlyReportSumVO;
import nc.vo.glcom.reckoning.monthlyReportVO;
import nc.vo.pub.BusinessException;
import nc.vo.pub.lang.UFBoolean;
import nc.vo.pub.lang.UFDouble;
import nc.vo.pubapp.pattern.exception.ExceptionUtils;
import nccloud.pubimpl.gl.lightgl.reckoning.ReckoningWebModel;
import nccloud.web.uapbd.pmbase.eps.action.EpsCommonConst;
import org.apache.commons.collections.map.HashedMap;

import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * @author duanyp1
 * @date 2024/3/15 10:44
 */
public class SettleValidateProcessor {

    private static final String langFileCode = "gl_reckoning";
    private SettleRecord record;
    private List<TempAccountDetail> tempAccountDetails;
    private Map<String, TempAccountAmount> tempAccountAmountMap;
    private Map<String, String> tempAccountNameMap;
    private StringBuilder errorMsg;
    private TimeWatch timeWatch;
    private Vector<monthlyReportVO> tempMonthlyReports;
    private GlDetailQueryBuilder queryBuilder;
    private boolean throwingException = false;
    private Map<String,String> contents;
    private Map<String,Boolean> fontRed;

    public boolean isThrowingException() {
        return throwingException;
    }

    public void init(SettleRecord record) {
        this.record = record;
        this.tempAccountDetails = GlBalanceUtil.queryGlBalanceData(record.getAccountingBook(), record.getYear(), record.getMonth());
        this.tempAccountNameMap = generateAccountCodeMap();
        this.tempAccountAmountMap = new HashMap<>();
        this.errorMsg = new StringBuilder();
        this.timeWatch = new TimeWatch(getClass().getName());
        this.queryBuilder = new GlDetailQueryBuilder(record);
        this.contents = new HashMap<>();
        this.fontRed = new HashMap<>();
        initAdd();
    }

    private void initAdd() {
        this.tempAccountNameMap.put("1", "资产");
        this.tempAccountNameMap.put("2", "负债");
        this.tempAccountNameMap.put("4", "收入");
        this.tempAccountNameMap.put("5", "费用");
        this.tempAccountNameMap.put("6", "预算收入");
        this.tempAccountNameMap.put("7", "预算支出");
        this.tempAccountNameMap.put("8101", "财政拨款结转");
        this.tempAccountNameMap.put("8201", "非财政拨款结转");
        this.tempAccountNameMap.put("1601", "固定资产");
        this.tempAccountNameMap.put("1602", "固定资产累计折旧");
        this.tempAccountNameMap.put("1701", "无形资产");
        this.tempAccountNameMap.put("1702", "无形资产累计摊销");
        this.tempAccountNameMap.put("720104", "资本性支出（基本建设）");
    }

    private Map<String, String> generateAccountCodeMap() {
        if (ObjectUtils.isEmpty(this.tempAccountDetails)) {
            return new HashMap<String ,String>();
        }
        return this.tempAccountDetails.stream()
                .collect(Collectors.toMap(TempAccountDetail::getItemcode, TempAccountDetail::getItemname, (old, val) -> old));
    }

    public void processReckoningWebModel(ReckoningWebModel reckoningModel) throws BusinessException {
        UFBoolean flag = SysInitQuery.getParaBoolean(EpsCommonConst.PK_GLOBE, "SValidate");
        if (UFBoolean.FALSE.equals(flag) || "true".equals(InvocationInfoProxy.getInstance().getProperty("skipDefCheck"))) return;
        this.tempMonthlyReports = new Vector<>();
        SettleRecord record = new SettleRecord(reckoningModel.getResultVo().getPk_accountingbook(), reckoningModel.getResultVo().getPresentYear(), reckoningModel.getResultVo().getPresentMonth());
        this.init(record);
        this.validate();
        String content = getMessage("reckoning_base_01");
        MonthlyReportSumVO def = new MonthlyReportSumVO();
        def.setStrRowOne(content);
        if (this.throwingException) {
            def.setStrRowTwo(getMessage("reckoning_base_02"));
            def.setStrRowThree(getMessage("reckoning_base_04"));
        } else {
            def.setStrRowTwo(getMessage("reckoning_base_03"));
            def.setStrRowThree("~");
        }
        monthlyReportVO temp = new monthlyReportVO();
        temp.setStrRowOne(content);
        temp.setIsBsuiLink("N");
        temp.setDirty(false);
        reckoningModel.getReportVec().add(temp);
        reckoningModel.getReportVec().addAll(this.tempMonthlyReports);
        //文字颜色
        reckoningModel.getFontSumRed().put(content, true);
        reckoningModel.getFontRed().put(content, true);
        reckoningModel.getFontRed().put(getMessage("reckoning_base_02"), true);
        reckoningModel.getFontRed().putAll(this.fontRed);
        reckoningModel.getResultVo().setIsReckoningble(true);
        reckoningModel.getResultVo().setIsReckoningble(!this.throwingException);
        def.setIsBsuiLink("N");
        def.setDirty(false);
        reckoningModel.getReportSumVec().add(def);

    }

    private void appendModuleContent(String content,String msg,boolean isPass){
        if(contents.containsKey(content)){
            return;
        }else {
            contents.put(content,null);
        }
        if(!isPass){
            this.fontRed.put(content,true);
            this.fontRed.put(msg,true);
        }
        monthlyReportVO temp = new monthlyReportVO();
        temp.setStrRowOne(content);
        temp.setStrRowTwo(msg);
        temp.setIsBsuiLink("N");
        temp.setDirty(false);
        this.tempMonthlyReports.add(temp);

    }

    private void appendModuleError(String content, String errorMsg) {
        if(!isThrowingException()){
            this.throwingException = true;
        }
        appendModuleContent(content,errorMsg,false);
    }
    private void appendModuleError(String content) {
        appendModuleContent(content,getMessage("reckoning_base_02"),false);
    }
    private void appendModuleSuccess(String content) {
        appendModuleContent(content,getMessage("reckoning_base_03"),true);
    }


    public void validate() {
        if (!needValidate()) return;
        Logger.debug("SettleAccount beginning : {" + record + "}");
        this.validateAccountAssistAmount();
        this.validateAccountAmount();
        Logger.debug("SettleAccount end ");
        if (errorMsg.length() > 0) {
            errorMsg.insert(0, getMessage("reckoning_00"));
            outPutError(errorMsg.toString());
            ExceptionUtils.wrappBusinessException(errorMsg.toString());
        }
        timeWatch.record();
    }

    private void outPutError(String msg) {
        LocalDateTime time = LocalDateTime.now();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
        String format = formatter.format(time);
        String path = RuntimeEnv.getInstance().getNCHome() +
                "/temp/" + this.tempAccountDetails.get(0).getOrgcode() + "_" + record.getYear() + record.getMonth() + "_" + format + ".txt";
        try (FileOutputStream outputStream = new FileOutputStream(path)) {
            outputStream.write(msg.getBytes(StandardCharsets.UTF_8));
            outputStream.flush();
        } catch (IOException e) {
            ExceptionUtils.wrappException(e);
        }
    }

    private boolean needValidate() {
        return !ObjectUtils.isEmpty(this.tempAccountDetails);
    }

    //科目合并检查
    private void validateAccountAmount() {
        //29、会计科目“400101财政基本拨款收入”贷方发生额等于“600101基本支出”贷方发生额
        String head = "29、会计科目“400101财政基本拨款收入”贷方发生额等于“600101基本支出”贷方发生额";
        timeWatch.start(head);
        validateCompareCreditAmount(head,"400101", "600101");
        //30、会计科目“400102财政项目拨款收入”贷方发生额等于“600102项目支出”贷方发生额
        head = "30、会计科目“400102财政项目拨款收入”贷方发生额等于“600102项目支出”贷方发生额";
        timeWatch.start(head);
        validateCompareCreditAmount(head,"400102", "600102");
        //31、会计科目“41010201科研收入”贷方发生额等于“61010201科教预算收入”贷方发生额
//        head = "31、会计科目“41010201科研收入”贷方发生额等于“61010201科教预算收入”贷方发生额";
//        timeWatch.start(head);
//        validateCompareCreditAmount(head,"41010201", "61010201");
        //32、会计科目“41010202教学收入”贷方发生额等于“61010202教学预算收入”贷方发生额
//        head = "32、会计科目“41010202教学收入”贷方发生额等于“61010202教学预算收入”贷方发生额";
//        timeWatch.start(head);
//        validateCompareCreditAmount(head,"41010202", "61010202");
        //33、科目4收入、科目5费用、科目6预算收入、科目7预算支出，期末余额为零
        head = "33、科目4收入、科目5费用、科目6预算收入、科目7预算支出，期末余额为零";
        timeWatch.start(head);
        validateFinalDataZero(head,"4", "5", "6", "7");
        //34、会计科目“720104资本性支出（基本建设）”借贷发生额均为零
        head = "34、会计科目“720104资本性支出（基本建设）”借贷发生额均为零";
        timeWatch.start(head);
        validateDebitSameCredit(head,"720104");
        //35、会计科目“1302020401化验材料（一级库）”期末余额为零
        head = "35、会计科目“1302020401化验材料（一级库）”期末余额为零";
        timeWatch.start(head);
        validateFinalDataZero(head,"1302020401");
        //36、会计科目“1302020501高值耗材”期末余额为零
        head = "36、会计科目“1302020501高值耗材”期末余额为零";
        timeWatch.start(head);
        validateFinalDataZero(head,"1302020501");
    }

    private void appendDefMsg(String head,List<TempAccountDetail> result, String langFileCode, Object... args) {
        if (!ObjectUtils.isEmpty(result)) {
            String[] rs = result.stream().map(TempAccountDetail::getNov).distinct().sorted().toArray(String[]::new);
            append(getMessage(langFileCode, args), Arrays.toString(rs));
        }else {
            append(head,null,true);
        }
    }




    //带辅助项检查
    private void validateAccountAssistAmount() {
        //1、会计分录中，项目编号“000”，对应的拨款种类“其他资金”
        String head = "1、会计分录中，项目编号“000”，对应的拨款种类“其他资金”";
        timeWatch.start(head);
        queryBuilder.clear();
        List<TempAccountDetail> result = queryBuilder
                .select(TempAccountDetail.cnov)
                .whereAssiValue(GlDocFreeItem.HRP005.getCode(), "000")
                .whereNotAssiValue(GlDocFreeItem.HRP004.getCode(), "05")
                .query();
        appendDefMsg(head,result,"reckoning_15");
        //2、如果项目编号“000”，对应的经费类型（HRP002）“其他经费”，则提示对应的凭证号
        head = "2、如果项目编号“000”，对应的经费类型“其他经费”，则提示对应的凭证号";
        timeWatch.start(head);
        queryBuilder.clear();
        result = queryBuilder
                .select(TempAccountDetail.cnov)
                .whereAssiValue(GlDocFreeItem.HRP005.getCode(), "000")
                .whereNotAssiValue(GlDocFreeItem.HRP002.getCode(), "05")
                .query();
        appendDefMsg(head,result,"reckoning_16");
        //3、会计分录中，项目编号“100”，对应的拨款种类“一般公共预算”或“政府性基金预算”、专项债券
        head = "3、会计分录中，项目编号“100”，对应的拨款种类“一般公共预算”、“政府性基金预算”或“专项债券”";
        timeWatch.start(head);
        queryBuilder.clear();
        result = queryBuilder
                .select(TempAccountDetail.cnov)
                .whereAssiValue(GlDocFreeItem.HRP005.getCode(), "100")
                .whereNotAssiValue(GlDocFreeItem.HRP004.getCode(), "01", "02","04")
                .query();
        appendDefMsg(head,result,"reckoning_17");
        //4、会计分录中，项目编号“100001”，对应的经费类型“财政基本拨款经费”
        //如果项目编号“100001”，对应的经费类型（HRP002）不为“01财政基本拨款经费”，则提示对应的凭证号
        head = "4、会计分录中，项目编号“100001”，对应的经费类型“财政基本拨款经费”";
        timeWatch.start(head);
        queryBuilder.clear();
        result = queryBuilder
                .select(TempAccountDetail.cnov)
                .whereAssiValue(GlDocFreeItem.HRP005.getCode(), "100001")
                .whereNotAssiValue(GlDocFreeItem.HRP002.getCode(), "01")
                .query();
        appendDefMsg(head,result,"reckoning_18");
        //5、会计分录中，项目编号“100002”，对应的经费类型“财政项目拨款经费”
        //如果项目编号“100002”，对应的经费类型（HRP002）不为“财政项目拨款经费”，则提示对应的凭证号
        head = "5、会计分录中，项目编号“100002”，对应的经费类型“财政项目拨款经费”";
        timeWatch.start(head);
        queryBuilder.clear();
        result = queryBuilder
                .select(TempAccountDetail.cnov)
                .whereAssiValue(GlDocFreeItem.HRP005.getCode(), "100002")
                .whereNotAssiValue(GlDocFreeItem.HRP002.getCode(), "02")
                .query();
        appendDefMsg(head,result,"reckoning_19");
        //6、会计分录中，项目编号“200”，对应的经费类型“科教经费”
        //如果项目编号“200”，对应的经费类型（HRP002）不为“03科教经费”，则提示对应的凭证号
        head = "6、会计分录中，项目编号“200”，对应的经费类型“科教经费”";
        timeWatch.start(head);
        queryBuilder.clear();
        result = queryBuilder
                .select(TempAccountDetail.cnov)
                .whereAssiValue(GlDocFreeItem.HRP005.getCode(), "200")
                .whereNotAssiValue(GlDocFreeItem.HRP002.getCode(), "03")
                .query();
        appendDefMsg(head,result,"reckoning_20");
        //7、会计分录中，项目编号“200”，项目余额是否有贷方负数。计算项目末级
        head = "7、会计分录中，项目编号“200”，项目余额是否有贷方负数";
        timeWatch.start(head);
        queryBuilder.clear();
        result = queryBuilder.select("b.code itemcode","b.name itemname").needAllAmount()
                .leftJoin("gl_docfree1","","gl_docfree1.assid = "+ GlDetailQueryUtil.VIEW_TABLE +".ASSID")
                .leftJoin("BD_DEFDOC","b","gl_docfree1.f24 = b.PK_DEFDOC")
                .whereAccountCode("6","7","8").delSy()
                .whereAssiValue(GlDocFreeItem.HRP005.getCode(),"200")
                .groupBy("b.code","b.name",TempAccountDetail.borrowing).query();
        if(!ObjectUtils.isEmpty(result)){
            Map<String, TempAccountDetail> detailMap = mergeAccountDetails(result,detail -> detail.getItemcode()+"/"+detail.getItemname());
            validateDefFinal(detailMap.values(), TempAccountDetail::getItemcode);
        }else {
            append(head,null,true);
        }
        //8、会计分录中，项目编号“200”，对应支出功能分类科目“其他专科医院”
        //如果项目编号“200”，对应支出功能分类科目(HRP-ZCGNFL)不为“2100208其他专科医院”，则提示对应的凭证号
        head = "8、会计分录中，项目编号“200”，对应支出功能分类科目“其他专科医院”";
        timeWatch.start(head);
        queryBuilder.clear();
        result = queryBuilder
                .select(TempAccountDetail.cnov)
                .whereAssiValue(GlDocFreeItem.HRP005.getCode(), "200")
                .whereNotAssiValue(GlDocFreeItem.HRPZCGNFL.getCode(), "2100208")
                .query();
        appendDefMsg(head,result,"reckoning_21");
        //9、会计分录中，项目编号“300”，对应支出功能分类科目“其他专科医院”
        //如果项目编号“300”，对应支出功能分类科目(HRP-ZCGNFL)不为“2100208其他专科医院”，则提示对应的凭证号
        head = "9、会计分录中，项目编号“300”，对应支出功能分类科目“其他专科医院”";
        timeWatch.start(head);
        queryBuilder.clear();
        result = queryBuilder
                .select(TempAccountDetail.cnov)
                .whereAssiValue(GlDocFreeItem.HRP005.getCode(), "300")
                .whereNotAssiValue(GlDocFreeItem.HRPZCGNFL.getCode(), "2100208")
                .query();
        appendDefMsg(head,result,"reckoning_22");
//        //10、项目编号“300”，项目余额是否有贷方负数。结转结余算期初余额+预算收入贷方发生-预算支出借方发生，剔除损益结转凭证(8开头的科目算期初+6开头的借方发生额-7开头的贷方发生额) 计算项目末级
        head = "10、项目编号“300”，项目余额是否有贷方负数";
        timeWatch.start(head);
        queryBuilder.clear();
        result = queryBuilder.select("b.code itemcode","b.name itemname").needAllAmount()
                .leftJoin("gl_docfree1","","gl_docfree1.assid = "+ GlDetailQueryUtil.VIEW_TABLE +".ASSID")
                .leftJoin("BD_DEFDOC","b","gl_docfree1.f24 = b.PK_DEFDOC")
                .whereAccountCode("6","7","8").delSy()
                .whereAssiValue(GlDocFreeItem.HRP005.getCode(),"300")
                .groupBy("b.code","b.name",TempAccountDetail.borrowing).query();
        if(!ObjectUtils.isEmpty(result)){
            Map<String, TempAccountDetail> detailMap = mergeAccountDetails(result,detail -> detail.getItemcode()+detail.getItemname());
            validateDefFinal(head,detailMap.values(), TempAccountDetail::getItemcode);
        }else {
            append(head,null,true);
        }
        //11、项目编号“300002”，对应的经费类型“04非同级财政拨款”
        head = "11、项目编号“300002”，对应的经费类型“非同级财政拨款”";
        timeWatch.start(head);
        queryBuilder.clear();
        result = queryBuilder
                .select(TempAccountDetail.cnov)
                .whereAssiValue(GlDocFreeItem.HRP005.getCode(), "300002")
                .whereNotAssiValue(GlDocFreeItem.HRP002.getCode(), "04")
                .query();
        if(result == null) return;
        appendDefMsg(head,result,"reckoning_10","300002");
        //12、项目编号“100”期末余额等于会计科目“8101\财政拨款结转”期末余额
        head = "12、项目编号“100”期末余额等于会计科目“8101财政拨款结转”期末余额";
        timeWatch.start(head);
        result = queryBuilder.clear().needInitNov().needFinalData().select("1").groupBy(TempAccountDetail.borrowing).delSy()
                .whereAssiValue(GlDocFreeItem.HRP005.getCode(), "100").query();
        TempAccountAmount p100 = generateTempAccountAmount("100", "", result);
        result = queryBuilder.clear().needInitNov().needFinalData().select("1").groupBy(TempAccountDetail.borrowing)
                .whereAccountCode("8101").query();
        TempAccountAmount a8101 = generateTempAccountAmount("8101", "", result);
        validate(head,p100, a8101, (a, b) -> a.sum_finalamount.compareTo(b.sum_finalamount) != 0,
                msg -> append(getMessage("reckoning_05", p100.code,p100.sum_finalamount, a8101.code + getAccountName(a8101.code),a8101.sum_finalamount)));
        //13、项目编号“200”期末余额等于会计科目“8201\非财政拨款结转”期末余额
        head = "13、项目编号“200”期末余额等于会计科目“8201非财政拨款结转”期末余额";
        timeWatch.start(head);
        result = queryBuilder.clear().needInitNov().needFinalData().needDebitData().needCreditData().delSy()
                .needInitData().select("1").groupBy(TempAccountDetail.borrowing)
                .whereAssiValue(GlDocFreeItem.HRP005.getCode(), "200").query();
        TempAccountAmount p200 = generateTempAccountAmount("200", "", result);
        result = queryBuilder.clear().needInitNov().needFinalData().select("1").groupBy(TempAccountDetail.borrowing)
                .whereAccountCode("8201").query();
        TempAccountAmount a8201 = generateTempAccountAmount("8201", "", result);
        validate(head,p200, a8201, (a, b) -> a.sum_finalamount.compareTo(b.sum_finalamount) != 0,
                msg -> append(getMessage("reckoning_05", p200.code,p200.sum_finalamount, a8201.code + getAccountName(a8201.code),a8201.sum_finalamount)));
        //14、项目编号“200”借方发生额等于会计科目“7201\事业支出”的经费类型“科教经费”借方发生额+“820101年初余额调整”借方发生额
        head = "14、项目编号“200”借方发生额等于会计科目“7201事业支出”的经费类型“科教经费”借方发生额+“820101年初余额调整”借方发生额";
        timeWatch.start(head);
        result = queryBuilder.clear().select("1").needAllAmount()
                .whereAssiValue(GlDocFreeItem.HRP002.getCode(),"03")
                .whereAccountCode("7201").groupBy(TempAccountDetail.borrowing).query();
        TempAccountAmount x720 = generateTempAccountAmount("7201", "", result);
        result = queryBuilder.clear().select("1").needAllAmount()
                .whereAccountCode("820101").groupBy(TempAccountDetail.borrowing).query();
        TempAccountAmount a820101 = generateTempAccountAmount("820101","",result);
        if (p200.sum_debitamount.compareTo(x720.sum_debitamount.add(a820101.sum_debitamount)) != 0) {
            appendModuleError(head);
            append(getMessage("reckoning_11", "200",p200.sum_debitamount, "7201事业支出",x720.sum_debitamount, "820101年初余额调整",a820101.sum_debitamount));
        }else {
            append(head,null,true);
        }
        //15、项目编号“200”贷方发生额等于会计科目“610102科教预算收入”贷方发生额+“820101年初余额调整”贷方发生额
        head = "15、项目编号“200”贷方发生额等于会计科目“610102科教预算收入”贷方发生额+“820101年初余额调整”贷方发生额";
        timeWatch.start(head);
        result = queryBuilder.clear().select("1").needAllAmount().delSy()
                .whereAccountCode("610102").groupBy(TempAccountDetail.borrowing).query();
        TempAccountAmount a610102 = generateTempAccountAmount("610102", "", result);
        TempAccountAmount a610102a820101 = add(a610102, a820101);
        validateSumCredit(head,p200, a610102a820101);
        //16、项目编号“300+000”期末余额等于会计科目“8301\专用结余+8202\非财政拨款结余+8501\其他结余”期末余额
        head = "16、项目编号“300+000”期末余额等于会计科目“8301专用结余+8202非财政拨款结余+8501其他结余”期末余额";
        timeWatch.start(head);
        result = queryBuilder.clear().select("1").needAllAmount().needInitNov().delSy()
                .whereAssiValue(GlDocFreeItem.HRP005.getCode(), "300", "000").groupBy(TempAccountDetail.borrowing).query();
        TempAccountAmount p300000 = generateTempAccountAmount("300_000","",result);
        result = queryBuilder.clear().select("1").needAllAmount().needInitNov()
                .whereAccountCode("8301").delSy()
                .groupBy(TempAccountDetail.borrowing).query();
        TempAccountAmount a8301 = generateTempAccountAmount("8301","",result);
        result = queryBuilder.clear().select("1").needAllAmount().needInitNov()
                .whereAccountCode("8202")
                .groupBy(TempAccountDetail.borrowing).query();
        TempAccountAmount a8202 = generateTempAccountAmount("8202","",result);
        result = queryBuilder.clear().select("1").needAllAmount().needInitNov()
                .whereAccountCode("8501")
                .groupBy(TempAccountDetail.borrowing).query();
        TempAccountAmount a8501 = generateTempAccountAmount("8501","",result);
        TempAccountAmount a8301a82028501 = add(a8301, a8202,a8501);
        validateSumFinalData(head,p300000, a8301a82028501);
        //17、项目编号“100001”借方发生额等于会计科目“5001\业务活动费用”+“5101\单位管理费用”的经费类型“01财政基本拨款经费”的借方发生额
        head ="17、项目编号“100001”借方发生额等于会计科目“5001业务活动费用”+“5101单位管理费用”的经费类型“财政基本拨款经费”的借方发生额";
        timeWatch.start(head);
        result = queryBuilder.clear().select("1").needAllAmount().delSy()
                .whereAssiValue(GlDocFreeItem.HRP005.getCode(), "100001").groupBy(TempAccountDetail.borrowing).query();
        TempAccountAmount p100001 = generateTempAccountAmount("100001","",result);
        result = queryBuilder.clear().select("1").needAllAmount()
                .whereAccountCode("5001")
                .whereAssiValue(GlDocFreeItem.HRP002.getCode(), "01")
                .groupBy(TempAccountDetail.borrowing).query();
        TempAccountAmount a5001 = generateTempAccountAmount("5001","",result);
        result = queryBuilder.clear().select("1").needAllAmount()
                .whereAssiValue(GlDocFreeItem.HRP002.getCode(), "01")
                .whereAccountCode("5101")
                .groupBy(TempAccountDetail.borrowing).query();
        TempAccountAmount x025101 = generateTempAccountAmount("5101","",result);
        if (p100001.sum_debitamount.compareTo(a5001.sum_debitamount.add(x025101.sum_debitamount)) != 0) {
            appendModuleError(head);
            append(getMessage("reckoning_11", "100001",p100001.sum_debitamount,
                    "5001业务活动费用",a5001.sum_debitamount, "5101单位管理费用",x025101.sum_debitamount));
        }else {
            append(head,null,true);
        }
        //18、项目编号“100001”贷方发生额等于会计科目“400101\财政拨款收入\财政基本拨款收入”贷方发生额
        head ="18、项目编号“100001”贷方发生额等于会计科目“400101财政拨款收入财政基本拨款收入”贷方发生额";
        timeWatch.start(head);
        result = queryBuilder.clear().select("1").needAllAmount()
                .whereAccountCode("400101").delSy()
                .groupBy(TempAccountDetail.borrowing).query();
        TempAccountAmount a400101 = generateTempAccountAmount("400101","",result);
        validateSumCredit(head,p100001, a400101);
        //19、项目编号“100001”借方发生额等于会计科目“7201\事业支出”的经费类型“01财政基本拨款经费”
        head ="19、项目编号“100001”借方发生额等于会计科目“7201事业支出”的经费类型“财政基本拨款经费”";
        timeWatch.start(head);
        result = queryBuilder.clear().select("1").needAllAmount()
                .whereAccountCode("7201").delSy()
                .whereAssiValue(GlDocFreeItem.HRP002.getCode(),"01")
                .groupBy(TempAccountDetail.borrowing).query();
        TempAccountAmount a7201 = generateTempAccountAmount("7201","",result);
        validateSumDebit(head,p100001, a7201);
        //20、项目编号“100001”贷方发生额等于会计科目“600101\财政拨款预算收入\基本支出”贷方发生额
        head ="20、项目编号“100001”贷方发生额等于会计科目“600101财政拨款预算收入基本支出”贷方发生额";
        timeWatch.start(head);
        result = queryBuilder.clear().select("1").needAllAmount()
                .whereAccountCode("600101").delSy()
                .groupBy(TempAccountDetail.borrowing).query();
        TempAccountAmount a600101 = generateTempAccountAmount("600101","",result);
        validateSumCredit(head,p100001, a600101);
        //21、项目编号“100002”借方发生额等于会计科目“7201\事业支出”的经费类型“02财政项目拨款经费”的借方发生额
        head ="21、项目编号“100002”借方发生额等于会计科目“7201事业支出”的经费类型“财政项目拨款经费”的借方发生额";
        timeWatch.start(head);
        result = queryBuilder.clear().select("1").needAllAmount()
                .whereAssiValue(GlDocFreeItem.HRP005.getCode(), "100002").delSy()
                .groupBy(TempAccountDetail.borrowing).query();
        TempAccountAmount p100002 = generateTempAccountAmount("100002","",result);
        result = queryBuilder.clear().select("1").needAllAmount()
                .whereAccountCode("7201").delSy()
                .whereAssiValue(GlDocFreeItem.HRP002.getCode(),"02")
                .groupBy(TempAccountDetail.borrowing).query();
        TempAccountAmount a7201_1 = generateTempAccountAmount("7201","",result);
        validateSumDebit(head,p100002, a7201_1);
        //22、项目编号“100002”贷方发生额等于会计科目“600102\财政拨款预算收入\项目支出”贷方发生额
        head ="22、项目编号“100002”贷方发生额等于会计科目“600102财政拨款预算收入项目支出”贷方发生额";
        timeWatch.start(head);
        result = queryBuilder.clear().select("1").needAllAmount()
                .whereAccountCode("600102").delSy()
                .groupBy(TempAccountDetail.borrowing).query();
        TempAccountAmount a600102 = generateTempAccountAmount("600102","",result);
        validateSumCredit(head,p100002, a600102);
        //23、会计科目“720105资本性支出”，银行存款现金流量为，投资或筹资类
        head ="23、会计科目“720105资本性支出”，银行存款现金流量为，投资或筹资类";
        timeWatch.start(head);
        List<TempAccountDetail> d720105 = getTempAccountDetailsByAccountCode("720105");
        if (!d720105.isEmpty()) {
            Map<String, String> novMap = d720105.stream().collect(Collectors.toMap(TempAccountDetail::getPk_voucher, TempAccountDetail::getNov, (o, n) -> o));
            Map<String, List<String>> map = new HashMap<>();
            novMap.forEach((k, v) -> {
                List<TempAccountDetail> d1002 = getTempAccountDetailsByAccountCode("1002").stream()
                        .filter(i -> i.getPk_voucher().equals(k)).filter(i -> i.getLocalcreditamount().compareTo(UFDouble.ZERO_DBL) > 0)
                        .collect(Collectors.toList());
                if (d1002.size() == 1) {
                    for (TempAccountDetail tempAccountDetail : d1002) {
                        if (tempAccountDetail.getLocalcreditamount().compareTo(UFDouble.ZERO_DBL) > 0
                                && tempAccountDetail.getLocaldebitamount().compareTo(UFDouble.ZERO_DBL) == 0
                                && !SettleAssistUtil.validateDocFreePrefix(new String[]{"ZK2", "ZK3"}, GlDocFreeItem.CASHFLOW.getCode(), tempAccountDetail.getAssid())) {
                            map.computeIfAbsent("1002", s -> new ArrayList<>()).add(v);
                        }
                    }
                } else {
                    List<TempAccountDetail> collect = d1002.stream()
                            .filter(tempAccountDetail -> SettleAssistUtil.validateDocFreePrefix(new String[]{"ZK2", "ZK3"}, GlDocFreeItem.CASHFLOW.getCode(), tempAccountDetail.getAssid())
                            ).collect(Collectors.toList());
                    if (!collect.isEmpty()) {
                        UFDouble val = UFDouble.ZERO_DBL;
                        UFDouble val1 = UFDouble.ZERO_DBL;
                        for (TempAccountDetail tempAccountDetail : collect) {
                            val = val.add(tempAccountDetail.getLocalcreditamount());
                        }
                        for (TempAccountDetail tempAccountDetail : d720105.stream().filter(tempAccountDetail -> tempAccountDetail.getPk_voucher().equals(k)).collect(Collectors.toList())) {
                            val1 = val1.add(tempAccountDetail.getLocaldebitamount());
                        }
                        if (val.compareTo(val1) != 0) {
                            map.computeIfAbsent("1002", s -> new ArrayList<>()).add(v);
                        }
                    }
                }
            });
            if (!map.isEmpty()) {
                appendModuleError(head);
                map.forEach((k, v) -> {
                    append(getMessage("reckoning_04", k), Arrays.toString(v.toArray()));
                });
            }
            else {
                append(head,null,true);
            }
        }
        //24、资产类科目1，辅助核算中涉及“客户档案”的，以最末级科目为基础核算的客户余额，不得为负数期初+借方发生-贷方发生不能小于0
        head ="24、资产类科目1，辅助核算中涉及“客户档案”的，以最末级科目为基础核算的客户余额，不得为负数。期初+借方发生-贷方发生不能小于0";
        timeWatch.start(head);
        result = queryBuilder.clear()
                .select(TempAccountDetail.cpk_accountingbook, TempAccountDetail.cassid, TempAccountDetail.cnov, TempAccountDetail.citemcode, TempAccountDetail.citemname)
                .needAllAmount().needInitNov()
                .whereAccountCode("1")
                .groupBy(TempAccountDetail.cpk_accountingbook, TempAccountDetail.cassid, TempAccountDetail.cnov, TempAccountDetail.citemcode, TempAccountDetail.citemname,TempAccountDetail.borrowing)
                .query();
        List<TempAccountDetail> t11 = result.stream()
                .filter(detail -> SettleAssistUtil.exist(GlDocFreeItem.CUSTOMER.getCode(), detail.getAssid()))
                .collect(Collectors.toList());
        validateFreeEndAmount(head,t11, true,GlDocFreeItem.CUSTOMER.getAssIdColumnCode());
        //25、资产类科目1，辅助核算中涉及“供应商档案”的，以最末级科目为基础核算的供应商余额，不得为负数期初+借方发生-贷方发生不能小于0
        head ="25、资产类科目1，辅助核算中涉及“供应商档案”的，以最末级科目为基础核算的供应商余额，不得为负数。期初+借方发生-贷方发生不能小于0";
        timeWatch.start(head);
        List<TempAccountDetail> t12 = result.stream().filter(detail -> SettleAssistUtil.exist(GlDocFreeItem.SUPPLIER.getCode(), detail.getAssid()))
                .collect(Collectors.toList());
        validateFreeEndAmount(head,t12, true,GlDocFreeItem.SUPPLIER.getAssIdColumnCode());
        //26、资产类科目1，辅助核算中涉及“人员档案”的，以最末级科目为基础核算的人员余额，不得为负数期初+借方发生-贷方发生不能小于0
        head ="26、资产类科目1，辅助核算中涉及“人员档案”的，以最末级科目为基础核算的人员余额，不得为负数。期初+借方发生-贷方发生不能小于0";
        timeWatch.start(head);
        List<TempAccountDetail> t13 = result.stream().filter(detail -> SettleAssistUtil.exist(GlDocFreeItem.PSNDOC.getCode(), detail.getAssid()))
                .collect(Collectors.toList());
        validateFreeEndAmount(head,t13, true,GlDocFreeItem.PSNDOC.getAssIdColumnCode());
        //27、会计科目“1601固定资产”、1602固定资产累计折旧、1701无形资产、1702无形资产累计摊销的经费类型，不存在“02财政基本拨款经费”
//        timeWatch.start("27、会计科目“1601固定资产”、1602固定资产累计折旧、1701无形资产、1702无形资产累计摊销的经费类型，不存在“02财政基本拨款经费”");
//        List<TempAccountDetail> d1601 = getTempAccountDetails(detail -> detail.getItemcode().startsWith("1601") &&
//                SettleAssistUtil.validateDocFree(new String[]{"02"}, GlDocFreeItem.HRP002.getCode(), detail.getAssid()));
//        validateFreeExistAccount(d1601, "1601");
//        List<TempAccountDetail> d1602 = getTempAccountDetails(detail -> detail.getItemcode().startsWith("1602") &&
//                SettleAssistUtil.validateDocFree(new String[]{"02"}, GlDocFreeItem.HRP002.getCode(), detail.getAssid()));
//        validateFreeExistAccount(d1602, "1602");
//        List<TempAccountDetail> d1701 = getTempAccountDetails(detail -> detail.getItemcode().startsWith("1701") &&
//                SettleAssistUtil.validateDocFree(new String[]{"02"}, GlDocFreeItem.HRP002.getCode(), detail.getAssid()));
//        validateFreeExistAccount(d1701, "1701");
//        List<TempAccountDetail> d1702 = getTempAccountDetails(detail -> detail.getItemcode().startsWith("1702") &&
//                SettleAssistUtil.validateDocFree(new String[]{"02"}, GlDocFreeItem.HRP002.getCode(), detail.getAssid()));
//        validateFreeExistAccount(d1702, "1702");
        //28、负债类科目2，辅助核算中涉及“供应商档案”的，以最末级科目为基础核算的供应商余额，不得为负数期初-借方发生+贷方发生不能小于0
        head ="28、负债类科目2，辅助核算中涉及“供应商档案”的，以最末级科目为基础核算的供应商余额，不得为负数。期初-借方发生+贷方发生不能小于0";
        timeWatch.start(head);
        result = queryBuilder.clear()
                .select(TempAccountDetail.cpk_accountingbook, TempAccountDetail.cassid, TempAccountDetail.cnov, TempAccountDetail.citemcode, TempAccountDetail.citemname)
                .needAllAmount().needInitNov()
                .whereAccountCode("2")
                .groupBy(TempAccountDetail.cpk_accountingbook, TempAccountDetail.cassid, TempAccountDetail.cnov, TempAccountDetail.citemcode, TempAccountDetail.citemname,TempAccountDetail.borrowing)
                .query();
        List<TempAccountDetail> t21 = result.stream().filter(detail -> SettleAssistUtil.exist(GlDocFreeItem.SUPPLIER.getCode(), detail.getAssid()))
                .collect(Collectors.toList());
        validateFreeEndAmount(head,t21, false,GlDocFreeItem.SUPPLIER.getAssIdColumnCode());
    }

    private String ufToString(UFDouble val){
        return val == null ? "" : val.setScale(UFDouble.ROUND_CEILING,UFDouble.ROUND_HALF_UP).toString();
    }

    private void validateDefFinal(Collection<TempAccountDetail> details , Function<TempAccountDetail,String> keyFunc) {
        details.forEach(v->{
            UFDouble val = v.getInitial_data().add(v.getLocalcreditamount()).sub(v.getLocalcreditamount());
            if (val.compareTo(UFDouble.ZERO_DBL) < 0) {
                append(getMessage("reckoning_09", keyFunc.apply(v)),
                        "期初："+ ufToString(v.getInitial_data()) +
                                " + 本期贷方: " + ufToString(v.getLocalcreditamount()) +
                                " - 本期借方: " + ufToString(v.getLocaldebitamount()) +
                                " = 余额：" + ufToString(val)
                );
            }
        });
    }
    private void validateDefFinal(String head,Collection<TempAccountDetail> details , Function<TempAccountDetail,String> keyFunc) {
        AtomicBoolean flag = new AtomicBoolean(true);
        details.forEach(v->{
            UFDouble val = v.getInitial_data().add(v.getLocalcreditamount()).sub(v.getLocalcreditamount());
            if (val.compareTo(UFDouble.ZERO_DBL) < 0) {
                append(getMessage("reckoning_09", keyFunc.apply(v)),
                        "期初："+ ufToString(v.getInitial_data()) +
                                " + 本期贷方: " + ufToString(v.getLocalcreditamount()) +
                                " - 本期借方: " + ufToString(v.getLocaldebitamount()) +
                                " = 余额：" + ufToString(val)
                );
                if(flag.get()){
                    flag.set(false);
                }
            }
        });
        if(flag.get()){
            append(head,null,true);
        }
    }

    private Map<String, TempAccountDetail> mergeAccountDetails(List<TempAccountDetail> details, Function<TempAccountDetail,String> keyFunc) {
        Map<String, TempAccountDetail> rs = new HashMap<>();
        details.forEach(detail -> {
            String key = keyFunc.apply(detail);
            TempAccountDetail tempAccountDetail = rs.get(key);
            if (tempAccountDetail == null) {
                tempAccountDetail = new TempAccountDetail();
                tempAccountDetail.setPk_accountingbook(detail.getPk_accountingbook());
                tempAccountDetail.setAssid(detail.getAssid());
                tempAccountDetail.setItemcode(detail.getItemcode());
                tempAccountDetail.setInitial_data(UFDouble.ZERO_DBL);
                tempAccountDetail.setLocaldebitamount(UFDouble.ZERO_DBL);
                tempAccountDetail.setLocalcreditamount(UFDouble.ZERO_DBL);
                tempAccountDetail.setLocaldebitamountsum(UFDouble.ZERO_DBL);
                tempAccountDetail.setLocalcreditamountsum(UFDouble.ZERO_DBL);
                tempAccountDetail.setFinal_data(UFDouble.ZERO_DBL);
            }
            UFDouble initialData = detail.getInitial_data() == null ? UFDouble.ZERO_DBL : detail.getInitial_data();
            UFDouble localdebitamount = detail.getLocaldebitamount() == null ? UFDouble.ZERO_DBL : detail.getLocaldebitamount();
            UFDouble localcreditamount = detail.getLocalcreditamount() == null ? UFDouble.ZERO_DBL : detail.getLocalcreditamount();
            UFDouble localdebitamountsum = detail.getLocaldebitamountsum() == null ? UFDouble.ZERO_DBL : detail.getLocaldebitamountsum();
            UFDouble localcreditamountsum = detail.getLocalcreditamountsum() == null ? UFDouble.ZERO_DBL : detail.getLocalcreditamountsum();
            UFDouble finalData = detail.getFinal_data() == null ? UFDouble.ZERO_DBL : detail.getFinal_data();
            tempAccountDetail.setInitial_data(tempAccountDetail.getInitial_data().add(initialData));
            tempAccountDetail.setLocaldebitamount(tempAccountDetail.getLocaldebitamount().add(localdebitamount));
            tempAccountDetail.setLocalcreditamount(tempAccountDetail.getLocalcreditamount().add(localcreditamount));
            tempAccountDetail.setLocaldebitamountsum(tempAccountDetail.getLocaldebitamountsum().add(localdebitamountsum));
            tempAccountDetail.setLocalcreditamountsum(tempAccountDetail.getLocalcreditamountsum().add(localcreditamountsum));
            tempAccountDetail.setFinal_data(tempAccountDetail.getFinal_data().add(finalData));
            rs.put(key, tempAccountDetail);
        });
        return rs;
    }

    private Map<String, String> getFreeDocVal(String assid){
        return (Map<String, String>) DaoUtil.executeQuery("SELECT * FROM gl_docfree1 where assid = '" + assid + "'", new MapProcessor());
    }

    private void validateFreeEndAmount(String head,List<TempAccountDetail> details, boolean debit,String columnCode) {
        String code;
        Function<TempAccountDetail,String> func = detail -> {
            Map<String, String> assItemVO = getFreeDocVal(detail.getAssid());
            return assItemVO.get(columnCode) + detail.getItemcode();
        };
        Map<String, TempAccountDetail> rs = mergeAccountDetails(details,func);
        boolean flag = true;
        for (TempAccountDetail temp : rs.values()) {
            UFDouble fi;
            if (debit) {
                fi = temp.getInitial_data().add(temp.getLocaldebitamount()).sub(temp.getLocalcreditamount());
                code = "reckoning_12";
            } else {
                fi = temp.getInitial_data().sub(temp.getLocaldebitamount()).add(temp.getLocalcreditamount());
                code = "reckoning_13";
            }
            StringBuilder assMsg = new StringBuilder();
            if (fi.compareTo(UFDouble.ZERO_DBL) < 0) {
                Map<String, String> assItemVO = getFreeDocVal(temp.getAssid());
                if (!ObjectUtils.isEmpty(assItemVO)) {
                    String supplierId = assItemVO.get(GlDocFreeItem.SUPPLIER.getAssIdColumnCode());
                    String customerId = assItemVO.get(GlDocFreeItem.CUSTOMER.getAssIdColumnCode());
                    String personId = assItemVO.get(GlDocFreeItem.PSNDOC.getAssIdColumnCode());
                    supplierId = "NN/A".equals(supplierId) ? null : supplierId;
                    customerId = "NN/A".equals(customerId) ? null : customerId;
                    personId = "NN/A".equals(personId) ? null : personId;
                    if (!ObjectUtils.isEmpty(supplierId)) {
                        assMsg.append(getMessage("reckoning_base_05"));
                        assMsg.append(SupplierAccessor.getDocByPk(supplierId).getName().getText());
                    }
                    if (!ObjectUtils.isEmpty(customerId)) {
                        assMsg.append(getMessage("reckoning_base_06"));
                        assMsg.append(CustomerAccessor.getDocByPk(customerId).getName().getText());
                    }
                    if (!ObjectUtils.isEmpty(personId)) {
                        assMsg.append(getMessage("reckoning_base_07"));
                        assMsg.append(PsnDocAccessor.getDocByPk(personId).getName().getText());
                    }
                }
                if(flag){
                    flag = false;
                    appendModuleError(head);
                }
                append(getMessage(code, temp.getItemcode() + getAccountName(temp.getItemcode()), fi), getMessage("reckoning_00", assMsg.toString()));
            }
        }
//        int c = 1 /0;
        if(flag){
            append(head,null,true);
        }
    }

    private void validateSumCredit(String head,TempAccountAmount A, TempAccountAmount B) {
        validate(head,A, B, (a, b) -> a.sum_creditamount.compareTo(b.sum_creditamount) != 0,
                msg -> append(getMessage("reckoning_06", A.code + getAccountName(A.code), A.sum_creditamount.setScale(UFDouble.ROUND_CEILING, UFDouble.ROUND_HALF_UP),
                        B.code + getAccountName(B.code), B.sum_creditamount,
                        A.sum_creditamount.sub(B.sum_creditamount).setScale(UFDouble.ROUND_CEILING, UFDouble.ROUND_HALF_UP).abs())));
    }

    private void validateSumFinalData(String head,TempAccountAmount A, TempAccountAmount B) {
        validate(head,A, B, (a, b) -> a.sum_finalamount.compareTo(b.sum_finalamount) != 0,
                msg -> append(getMessage("reckoning_07", A.code + getAccountName(A.code), A.sum_finalamount.setScale(UFDouble.ROUND_CEILING, UFDouble.ROUND_HALF_UP),
                        B.code + getAccountName(B.code), B.sum_finalamount.setScale(UFDouble.ROUND_CEILING, UFDouble.ROUND_HALF_UP),
                        A.sum_finalamount.sub(B.sum_finalamount).setScale(UFDouble.ROUND_CEILING, UFDouble.ROUND_HALF_UP).abs())));
    }

    private void validateSumDebit(String head,TempAccountAmount A, TempAccountAmount B) {
        validate(head,A, B, (a, b) -> a.sum_debitamount.compareTo(b.sum_debitamount) != 0,
                msg -> append(getMessage("reckoning_08", A.code + getAccountName(A.code), A.sum_debitamount.setScale(UFDouble.ROUND_CEILING, UFDouble.ROUND_HALF_UP),
                        B.code + getAccountName(B.code), B.sum_debitamount.setScale(UFDouble.ROUND_CEILING, UFDouble.ROUND_HALF_UP),
                        A.sum_debitamount.sub(B.sum_debitamount).setScale(UFDouble.ROUND_CEILING, UFDouble.ROUND_HALF_UP).abs())));
    }

    private void validate(String head,TempAccountAmount A, TempAccountAmount B, BiPredicate<TempAccountAmount, TempAccountAmount> condition,
                          Consumer<StringBuilder> errorMsgSupplier) {
        if (condition.test(A, B)) {
            appendModuleError(head);
            errorMsgSupplier.accept(errorMsg);
        }else {
            append(head,null,true);
        }
    }

    private void validate(String code, Predicate<TempAccountAmount> condition,
                          BiConsumer<StringBuilder, TempAccountAmount> errorMessageSupplier) {
        TempAccountAmount amount = getTempAccountAmountByAccountCode(code);
        if (condition.test(amount)) {
            errorMessageSupplier.accept(errorMsg, amount);
        }
    }

    private void validate(String head,String code, Predicate<TempAccountAmount> condition,
                          BiConsumer<StringBuilder, TempAccountAmount> errorMessageSupplier) {
        TempAccountAmount amount = getTempAccountAmountByAccountCode(code);
        if (condition.test(amount)) {
            appendModuleError(head);
            errorMessageSupplier.accept(errorMsg, amount);
        }else {
            append(head,null,true);
        }
    }

    public void validateCompareCreditAmount(String head,String codeA, String codeB) {
        validate(head,codeA, amount -> amount.sum_creditamount.compareTo(getTempAccountAmountByAccountCode(codeB).sum_creditamount) != 0,
                (msg, amount) -> append(getMessage("reckoning_01",
                        amount.code + getAccountName(amount.code), amount.sum_creditamount.setScale(UFDouble.ROUND_CEILING, UFDouble.ROUND_HALF_UP),
                        getTempAccountAmountByAccountCode(codeB).code, getTempAccountAmountByAccountCode(codeB).sum_creditamount.setScale(UFDouble.ROUND_CEILING, UFDouble.ROUND_HALF_UP),
                        getTempAccountAmountByAccountCode(codeB).sum_creditamount.sub(amount.sum_creditamount).setScale(UFDouble.ROUND_CEILING, UFDouble.ROUND_HALF_UP).abs()))
        );
    }

    //借贷金额是否一样
    public void validateDebitSameCredit(String head,String... codes) {
        for (String code : codes) {
            validate(head,code, amount -> amount.sum_debitamount.compareTo(amount.sum_creditamount) != 0,
                    (msg, amount) -> append(getMessage("reckoning_02", amount.code + amount.name))
            );
        }
    }

    //期末余额是否为零
    public void validateFinalDataZero(String head,String... codes) {
        for (String code : codes) {
            validate(head,code, amount -> amount.sum_finalamount.compareTo(UFDouble.ZERO_DBL) != 0,
                    (msg, amount) -> append(getMessage("reckoning_03", amount.code + amount.name), amount.sum_finalamount.setScale(UFDouble.ROUND_CEILING, UFDouble.ROUND_HALF_UP).toString())
            );
        }
    }

    private String getAccountName(String accountCode) {
        String name;
        return (name = this.tempAccountNameMap.get(accountCode)) == null ? "" : name;
    }

    private String getMessage(String code, Object... args) {
        for (int i = args.length - 1; i >= 0; i--) {
            if (args[i] == null) {
                args[i] = "~";
            } else if (args[i] instanceof UFDouble) {
                args[i] = ((UFDouble) args[i]).toDouble();
            }
        }
        return NCDefLangUtil.getMessage(langFileCode, code, args);
    }

    private List<TempAccountDetail> getTempAccountDetailsByAccountCode(String accountCode) {
        return getTempAccountDetails(detail -> detail.getItemcode().startsWith(accountCode));
    }

    private List<TempAccountDetail> getTempAccountDetails(Predicate<TempAccountDetail> func) {
        return this.tempAccountDetails.stream().filter(func).collect(Collectors.toList());
    }

    private TempAccountAmount getTempAccountAmountByAccountCode(String accountCode) {
        return getTempAccountAmount(MapKey.accountCode, accountCode, () -> getTempAccountDetailsByAccountCode(accountCode));
    }

    private TempAccountAmount getTempAccountAmount(String typeKey, String accountCode, Supplier<List<TempAccountDetail>> func) {
        String key = typeKey + accountCode;
        if (!tempAccountAmountMap.containsKey(key)) {
            List<TempAccountDetail> details = func.get();
            String accountName = tempAccountNameMap.get(accountCode);
            if (details.isEmpty()) {
                tempAccountAmountMap.put(key, new TempAccountAmount(accountCode, accountName));
            } else {
                TempAccountAmount accountAmount = generateTempAccountAmount(accountCode, accountName, details);
                tempAccountAmountMap.put(key, accountAmount);
                return accountAmount;
            }
        }
        return tempAccountAmountMap.get(key);
    }


    private TempAccountAmount generateTempAccountAmount(String code, String name, List<TempAccountDetail> details) {
        return details.stream().reduce(new TempAccountAmount(code, name),
                (tempAccountAmount, detail) -> new TempAccountAmount(code, name,
                        tempAccountAmount.sum_initial_data.add(detail.getInitial_data() == null ? UFDouble.ZERO_DBL : detail.getInitial_data()),
                        tempAccountAmount.sum_debitamount.add(detail.getLocaldebitamount() == null ? UFDouble.ZERO_DBL : detail.getLocaldebitamount()),
                        tempAccountAmount.sum_creditamount.add(detail.getLocalcreditamount() == null ? UFDouble.ZERO_DBL : detail.getLocalcreditamount()),
                        tempAccountAmount.sum_finalamount.add(detail.getFinal_data() == null ? UFDouble.ZERO_DBL : detail.getFinal_data())),
                (tempAccountAmountA, tempAccountAmountB) -> new TempAccountAmount(code, name,
                        tempAccountAmountA.sum_initial_data.add(tempAccountAmountB.sum_initial_data),
                        tempAccountAmountA.sum_debitamount.add(tempAccountAmountB.sum_debitamount),
                        tempAccountAmountA.sum_creditamount.add(tempAccountAmountB.sum_creditamount),
                        tempAccountAmountA.sum_finalamount.add(tempAccountAmountB.sum_finalamount)));
    }

    private void append(String msg) {
        append(msg, null);
    }

    private void append(String content, String msg) {
//        errorMsg.append(content).append(":").append(msg).append("\n");
        appendModuleError(content, msg);
    }

    private void append(String content, String msg,boolean isPass) {
//        errorMsg.append(content).append(":").append(msg).append("\n");
        if(isPass){
            appendModuleSuccess(content);
        }else {
            appendModuleError(content, msg);
        }
    }

    TempAccountAmount add(TempAccountAmount A, TempAccountAmount B) {
        return new TempAccountAmount(
                "(" + A.code + "、" + B.code + ")",
                "(" + A.name + "、" + B.name + ")",
                A.sum_initial_data.add(B.sum_initial_data),
                A.sum_debitamount.add(B.sum_debitamount),
                A.sum_creditamount.add(B.sum_creditamount),
                A.sum_finalamount.add(B.sum_finalamount)
        );
    }
    TempAccountAmount add(TempAccountAmount A, TempAccountAmount B,TempAccountAmount C) {
        return new TempAccountAmount(
                "(" + A.code + "、" + B.code + "、"+C.code+")",
                "(" + A.name + "、" + B.name + "、"+C.name+")",
                A.sum_initial_data.add(B.sum_initial_data).add(C.sum_initial_data),
                A.sum_debitamount.add(B.sum_debitamount).add(C.sum_debitamount),
                A.sum_creditamount.add(B.sum_creditamount).add(C.sum_creditamount),
                A.sum_finalamount.add(B.sum_finalamount).add(C.sum_finalamount)
        );
    }

    interface MapKey {
        String projectCode = "projectCode";
        String accountCode = "accountCode";
    }

    private static class TempAccountAmount {
        String code;
        String name;
        UFDouble sum_initial_data;
        UFDouble sum_debitamount;
        UFDouble sum_creditamount;
        UFDouble sum_finalamount;

        TempAccountAmount(String code, String name, UFDouble sum_initial_data, UFDouble sum_debitamount, UFDouble sum_creditamount, UFDouble sum_finalamount) {
            this.code = code;
            this.name = name;
            this.sum_initial_data = sum_initial_data;
            this.sum_debitamount = sum_debitamount;
            this.sum_creditamount = sum_creditamount;
            this.sum_finalamount = sum_finalamount;
        }

        TempAccountAmount(String code, String name) {
            this(code, name, UFDouble.ZERO_DBL, UFDouble.ZERO_DBL, UFDouble.ZERO_DBL, UFDouble.ZERO_DBL);
        }
    }

}
